package elsim.entities;

import java.util.Comparator;
import java.util.List;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Ordering;
import com.google.common.primitives.Ints;

/**
 * I'm tired of Groovy, I'm tired of it's poor typing system, and yes it
 * includes duck typing in general. I'm tired of poor IDE support, I'm tired of
 * illogical mix of functional and imperative concepts - if we want functional,
 * let's do Scala or Clojure, if we want imperative, let's stay where we are.
 *
 * That is, good old Java. Fine for algorithms, I guess.
 */
public class PassengerAlgorithms {
  public static class PassengerComparator implements Comparator<Passenger> {
    private final int destination;

    public PassengerComparator(int destination) {
      this.destination = destination;
    }

    @Override
    public int compare(Passenger o1, Passenger o2) {
      return Ints.compare(o1.distanceFromDestinationTo(destination),
          o2.distanceFromDestinationTo(destination));
    }
  }

  public static Lift findBestLift(Iterable<Lift> lifts, final int from, final int to) {
    return Ordering.from(new Comparator<Lift>() {
      @Override
      public int compare(Lift o1, Lift o2) {
        return Ints.compare(transitionCost(o1, from, to), transitionCost(o2, from, to));
      }
    }).min(lifts);
  }

  public static Direction getDirectionFromTo(int from, int to) {
    if (to > from) {
      return Direction.UP;
    } else {
      return Direction.DOWN;
    }
  }

  @VisibleForTesting
  static boolean isPointAlongTheWay(Lift lift, int point) {
    Direction liftDirection = lift.getDirection();

    if (point > lift.getPosition() && liftDirection.equals(Direction.UP)
        || point < lift.getPosition() && liftDirection.equals(Direction.DOWN)) {
      return true;
    } else {
      return false;
    }
  }

  public static Passenger mostDistantPassenger(int position, List<Passenger> passengers) {
    return Ordering.from(new PassengerComparator(position)).max(passengers);
  }

  /**
   * Transition cost means how much would it take for lift to:
   *
   * 1. Get from where it is now to the point where it needs to pick up the
   * passenger (specified by 'from')
   *
   * 2. Get passenger from 'from' to 'to'
   *
   * Here're two basic scenarios: it's either along the way lift goes right now,
   * or it's not. If it is along the way (that is, both pick-up point and where
   * passenger wants to get to are along the direction lift is currently
   * moving), then it's all easy - it's just a direct distance.
   *
   * But when it's not exactly, or completely not along the way, things start to
   * get funny. Few more cases:
   *
   * 1. Pick-up point is along the way, but destination isn't
   *
   * 2. Pick-up point is along the way, but it will happen after last passenger
   * has left (and lift has no more direction to go so it can go anywhere at
   * all).
   *
   * 3. Pick-up point is along the way, and drop-off point is not along the way
   *
   * 4. Pick-up and drop-off points are not along the way
   */
  public static int transitionCost(Lift lift, int from, int to) {
    // if it's broken, it's broken
    if (!lift.isOperational()) {
      return Integer.MAX_VALUE;
    }

    int mostDistantPointLiftGoes = lift.computeFinalDestination();
    if (isPointAlongTheWay(lift, from)) {
      if (isPointAlongTheWay(lift, to)) {
        // this is a basic scenario, both points are along the way
        return Math.abs(from - to);
      } else {
        // this is a bit more complicated - destination is not along the way
        return Math.abs(lift.getPosition() - mostDistantPointLiftGoes)
            + Math.abs(mostDistantPointLiftGoes - to);
      }
    } else {
      // here we go
      int fromHereToFinal = Math.abs(lift.getPosition() - mostDistantPointLiftGoes);
      int requestedDistance = Math.abs(from - to);
      int fromFinalToPickupDist = Math.abs(mostDistantPointLiftGoes - from);

      return fromHereToFinal + requestedDistance + fromFinalToPickupDist;
    }
  }
}
